#region License Revision: 0 Last Revised: 6/18/2008 11:00 AM
/******************************************************************************
Copyright (c) Microsoft Corporation.  All rights reserved.


This file is licensed under the Microsoft Public License (Ms-PL). A copy of the Ms-PL should accompany this file. 
If it does not, you can obtain a copy from: 

http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx
******************************************************************************/
#endregion // License
using System;
using System.Windows.Browser;
using System.Diagnostics;
using System.Collections.Generic;
using ScriptInterop;

namespace VIEWS // Virtual Earth Wrapper
{

	/// <summary>Shape object used for pushpins, polygons, and polylines on the map</summary>
	public class VEShape : JSObject, IDisposable
	{
		/// <summary>String holding JavaScript class name</summary>
		private static string thisClass = "VEShape";

		/// <summary>
		/// A lookup table for scriptObjects so we can associate existing shape objects with their appropriate wrappers
		/// </summary>
		private static Dictionary<ScriptObject, VEShape> shapeLookup = new Dictionary<ScriptObject, VEShape>();

		/// <summary>
		/// Clear out all the shapes from the shape lookup table, start fresh
		/// </summary>
		public static void PurgeAllShapes()
		{
			shapeLookup.Clear();
		}

		public static Dictionary<ScriptObject, VEShape> Shapes
		{
			get { return shapeLookup; }
		}

		/// <summary>Get the JavaScript class name</summary>
		/// <returns type="String">The class name used in JavaScript </returns>		
		public override string ClassName
		{
			get { return thisClass; }
		}

		/// <summary>Determine if the passed object is indeed a shape</summary>
		/// <param name="obj">the object to test for shapeness</param>
		/// <returns type="bool">True if it's a shape (VEShape), and false otherwise.</returns>		
		static public bool IsShape(ScriptObject obj)
		{
			return (bool)HtmlPage.Window.Invoke("IsShape", obj);
		}

		/// <summary>Construct a C# wrapper from a ScriptObject</summary>
		/// <param name="so">A ScriptObjet from JScript.</param>
		protected VEShape(ScriptObject so) : base(so)
		{
			if (shapeLookup.ContainsKey(so))
				throw new InvalidOperationException("use GetShapeObject() API instead, duplicate wrapper instances!");
			// add to shape table
			shapeLookup[ScriptObject] = this;
		}

		/// <summary>
		/// Finalizer for the shape object, in case nobody called Dispose, do it here
		/// </summary>
		~VEShape()
		{
			Dispose(false);
		}

		/// <summary>Initializes a new instance of the VEShape class.</summary>
		/// <param name="type" type="VEShapeType">A VEShapeType Enumeration value of type that represents the type of shape. Required.</param>
		/// <param name="points">If the shape is a pushpin, this parameter can either be a single VELatLong Class object or an array of VELatLong objects. If it is an array of VELatLong objects, only the first VELatLong object is used to define the pushpin's location. Any additional data members are ignored. If the shape is a polyline or polygon, it must be an array of VELatLong objects, containing at least two points for a polyline and at least three points for a polygon. Required.</param>
		public VEShape(VEShapeType type, VELatLong[] points)
		{
			JSArray<ScriptObject> arrPoint = ConvertToScriptObjectJSArray(points);
			int ilim = points.Length;
			Debug.Assert(ilim == 1 || type != VEShapeType.Pushpin, "Only single points with pushpins please");
			ScriptObject = CreateClass(ScriptEnumConverter.ToString(type), (ScriptObject)arrPoint);
			// add to shape table
			shapeLookup[ScriptObject] = this;
		}

		/// <summary>Initializes a new instance of the VEShape class.</summary>
		/// <param name="type" type="VEShapeType">A VEShapeType Enumeration value of type that represents the type of shape. Required.</param>
		/// <param name="points">If the shape is a pushpin, this parameter can either be a single VELatLong Class object or an array of VELatLong objects. If it is an array of VELatLong objects, only the first VELatLong object is used to define the pushpin's location. Any additional data members are ignored. If the shape is a polyline or polygon, it must be an array of VELatLong objects, containing at least two points for a polyline and at least three points for a polygon. Required.</param>
		public VEShape(VEShapeType type, VELatLong point)
		{
			Debug.Assert(type == VEShapeType.Pushpin, "Did you want to pass an array of points instead?");
			ScriptObject = CreateClass(ScriptEnumConverter.ToString(type), (ScriptObject)point);
			// add to shape table
			shapeLookup[ScriptObject] = this;
		}

		/// <summary>Returns the specific type of shape from a general shape type</summary>
		/// <param name="so">A ScriptObjet from JScript.</param>
		/// <returns>propertly typed Shape object, or null if it's not a shape</returns>
		static public VEShape GetShapeObject(ScriptObject obj)
		{
			// first check to see if it's a shape type, if it's not just return null
			if (!VEShape.IsShape(obj))
				return null;
			// check to see if we already have an instance for this object
			VEShape shape;
			// not found in generic shape lookup, so make a new one
			if (!shapeLookup.ContainsKey(obj))
			{
				VEShapeType type = ScriptEnumConverter.ToShapeType((string)obj.Invoke("GetShapeType"));

				// create the correct type based on the shape type
				if (type == VEShapeType.Pushpin)
				{
					shape = new VEPushpin(obj);
				}
				else if (type == VEShapeType.Polygon)
				{
					shape = new VEPolygon(obj);
				}
				else if (type == VEShapeType.Polyline)
				{
					shape = new VEPolyline(obj);
				}
				else
				{
					shape = new VEShape(obj);
				}
			}
			else
			{
				shape = shapeLookup[obj];
			}
			return shape;
		}

        /// <summary>
        /// Converts a JavaScript array to an array of <see cref="VEShape"/> objects with the proper type.
        /// </summary>
        /// <param name="scriptArray">
        /// The script object representing the JavaScript array.
        /// </param>
        /// <returns>
        /// An array of <see cref="VEShape"/> objects with the proper type.
        /// </returns>
        static public VEShape[] ConvertShapeArray(ScriptObject scriptObject)
        {
            // If no shapes, bail
            if (scriptObject == null) { return new VEShape[0]; }
            
            // Get JSArray
            JSArray<ScriptObject> scriptArray = new JSArray<ScriptObject>(scriptObject);

            // Convert
            VEShape[] shapes = new VEShape[scriptArray.Length];
            for (int i = 0; i < shapes.Length; ++i)
            {
                shapes[i] = GetShapeObject(scriptArray[i]);
            }

            // Done
            return shapes;
        }

		/// <summary>Hides the specified VEShape object from view.</summary>
		public void Hide()
		{
			Invoke("Hide");
		}

		/// <summary>Shows the specified VEShape object that was previously hidden.</summary>
		public void Show()
		{
			Invoke("Show");
		}

		/// <summary>Returns whether the shape is a model.</summary>
		/// <returns type="Boolean"></returns>
		public bool IsModel()
		{
			return Invoke<bool>("IsModel");
		}


		// C# properties to make calling the APIs easier, those specific to a particular type are in shape specific subclasses:

        /// <summary>Gets the internal identifier of the VEShape object.  Use this identifier when working with VEShape object events. A VEShape object only has an identifier when it has been added to a layer. If you try to get the identifier of a VEShape object that is not part of a layer, the VEShape.GetID method returns null.</summary>
        /// <returns type="String"></returns>
        public string Id
        {
            get
            {
                //ScriptObject so = Invoke<ScriptObject>("GetID");
                //Debug.Assert(so == null, "Shape not part of a layer");
                //if (so == null)
                // return "";
                //return so.ConvertTo<string>();
                return Invoke<string>("GetID");
            }
        }

		/// <summary>Specifies the altitude for a Pushpin shape.</summary>
		public double Altitude
		{
            get { return Invoke<double>("GetAltitude"); }
            set { Invoke("SetAltitude", value, ScriptEnumConverter.ToString(AltitudeMode)); }
		}

		/// <summary>Specifies the mode in which a shape's altitude is represented.</summary>
		public VEAltitudeMode AltitudeMode
		{
            get { return ScriptEnumConverter.ToAltitudeMode(Invoke<string>("GetAltitudeMode")); }
            set { Invoke("SetAltitudeMode", ScriptEnumConverter.ToString(value)); }
		}

        /// <summary>the VEShape object's custom icon in either a URL to an image, or custom HTML form.</summary>
		public string CustomIcon
		{
			get { return Invoke<string>("GetCustomIcon"); }
            set { Invoke("SetCustomIcon", value); }
		}

        /// <summary>the VEShape object's custom icon in VECustomIconSpecification form</summary>
		public VECustomIconSpecification CustomIconSpecification
		{
			get
            {
                ScriptObject so = Invoke<ScriptObject>("GetCustomIcon");
                VECustomIconSpecification customIcon = new VECustomIconSpecification(so);
                return customIcon;
            }
            set { Invoke("SetCustomIcon", (ScriptObject)value); }
		}

        /// <summary>The description of the VEShape object.</summary>
		public string Description
		{
            get { return Invoke<string>("GetDescription"); }
            set { Invoke("SetDescription", value); }
		}

        /// <summary type="Boolean">Specifies whether a line is drawn from the shape to the ground.</summary>
		public bool LineToGround
		{
			get
            {
                ScriptObject so = Invoke<ScriptObject>("GetLineToGround");
                Debug.Assert(so == null, "No Line to ground for pushpins");
                if (so == null)
                    return false;
                return so.ConvertTo<bool>();
            }
            set { Invoke("SetLineToGround", value); }
		}
		
		/// <summary>The maximum zoom level at which the shape is visible.</summary>
		public int MaxZoomLevel
		{
            get { return Invoke<int>("GetMaxZoomLevel"); }
            set { Invoke("SetMaxZoomLevel", value); }
		}

        /// <summary>The maximum zoom level at which the shape is visible.</summary>
		public int MinZoomLevel
		{
            get { return Invoke<int>("GetMinZoomLevel"); }
            set { Invoke("SetMinZoomLevel", value); }
		}

        /// <summary>The shape's "more info" URL.</summary>
		public Uri MoreInfoUrl
		{
            get
            {
                string url = Invoke<string>("GetMoreInfoURL");
                if (string.IsNullOrEmpty(url))
                {
                    return null;
                }
                else
                {
                    return new Uri(url);
                }
            }
            set
            {
                Invoke("SetMoreInfoURL", value.ToString());
            }
		}

        /// <summary>The shape's photo URL.</summary>
		public Uri PhotoUrl
		{
            get
            {
                string url = Invoke<string>("GetPhotoURL");
                if (string.IsNullOrEmpty(url))
                {
                    return null;
                }
                else
                {
                    return new Uri(url);
                }
            }
            set
            {
                Invoke("SetPhotoURL", value.ToString());
            }
		}

        /// <summary>Gets or sets the points of the VEShape Class object.</summary>
        public VELatLong[] Points
        {
            get
            {
                return ConvertToArray<VELatLong>(Invoke<ScriptObject>("GetPoints"));
            }
            set
            {
                JSArray<ScriptObject> arrPoint = ConvertToScriptObjectJSArray(value);
                Invoke("SetPoints", (ScriptObject)arrPoint);
            }
        }

        /// <summary>Gets the reference to the layer containing the specified VEShape object.</summary>
        /// <returns type="VEShapeLayer"></returns>
        public VEShapeLayer ShapeLayer
        {
            get
            {
                ScriptObject so = Invoke<ScriptObject>("GetShapeLayer");
                VEShapeLayer shapeLayer = new VEShapeLayer(so);
                return shapeLayer;
            }
        }

        /// <summary>Gets the type of the VEShape object.</summary>
        /// <returns type="VEShapeType">string telling the type of shape</returns>
        public VEShapeType ShapeType // renamed to not conflict with default object method
        {
            get
            {
                return ScriptEnumConverter.ToShapeType(Invoke<string>("GetType"));
            }
        }

        /// <summary>The title of the VEShape object. This title will be displayed in the shape's info box.</summary>
		public string Title
		{
            get { return Invoke<string>("GetTitle"); }
            set { Invoke("SetTitle", value); }
		}

        /// <summary>An integer specifying the z-index for the shape's icon (or for the pushpin if the shape is a pushpin).</summary>
		public int ZIndex
		{
            get { return Invoke<int>("GetZIndex"); }
            set { Invoke("SetZIndex", value); }
		}

		#region IDisposable Members
        protected virtual void Dispose(bool disposing)
        {
            if (ScriptObject != null)
            {
                shapeLookup.Remove(ScriptObject);
                ScriptObject = null;
            }
        }
		
        public void Dispose()
		{
            Dispose(true);
            GC.SuppressFinalize(this);
        }

		#endregion
	}

	/// <summary>VEPolyShape class for polygons and polylines.</summary>
	public class VEPolyShape: VEShape
	{
		/// <summary>Construct a C# wrapper from a ScriptObject</summary>
		/// <param name="so">A ScriptObjet from JScript.</param>
		public VEPolyShape(ScriptObject so) : base(so) { }

		/// <summary>Initializes a new instance of the VEPolyShape class.</summary>
		/// <param name="type" type="VEShapeType">A VEShapeType Enumeration value of type that represents the type of shape. Required.</param>
		/// <param name="points">An array of VELatLong objects. It must contain at least two points for a polyline and at least three points for a polygon. Required.</param>
		public VEPolyShape(VEShapeType type, VELatLong[] points) : base(type, points) {}
		
		/// <summary>Hides the icon associated with a polyline or polygon. This method is ignored for pushpins.</summary>
		public void HideIcon()
		{
			Invoke("HideIcon");
		}


		/// <summary>Sets the maximum zoom level at which the shape is visible.</summary>
		/// <param name="icon" type="Number">Optional. An integer specifying the z-index for the shape's icon (or for the pushpin if the shape is a pushpin). If this value is null or undefined the z-index is not changed.</param>
		/// <param name="polyShape" type="Number">Optional. An integer specifying the z-index for the shape. This parameter is ignored if the shape is a pushpin. If this value is null or undefined the z-index is not changed.</param>
		public void SetZIndex(int icon, int polyShape)
		{
			Invoke("SetZIndex", icon, polyShape);
		}
		
		/// <summary>Shows the icon associated with a polyline or polygon. This method is ignored for pushpins.</summary>
		public void ShowIcon()
		{
			Invoke("ShowIcon");
		}

        /// <summary>Specifies the altitude for a Polyline or Polygon shape.</summary>
		public double[] AltitudePolyShape
		{
			get
            {
                Debug.Assert(ShapeType != VEShapeType.Pushpin, "This method is only for for Polygons and Polylines");
                return Invoke<double[]>("GetAltitude");
            }
            set { Invoke<bool>("SetAltitude", value, ScriptEnumConverter.ToString(AltitudeMode)); }
		}

        /// <summary type="VELatLong">The info box anchor of the VEShape object.</summary>
		public VELatLong IconAnchor
		{
			get
            {
                ScriptObject so = Invoke<ScriptObject>("GetIconAnchor");
                VELatLong latlong = new VELatLong(so);
                return latlong;
            }
			set
            {
                Invoke("SetIconAnchor", (ScriptObject)value);
            }
		}

		/// <summary type="VEColor">The line color or transparency for a polyline or polygon.</summary>
		public VEColor LineColor
		{
			get
            {
                ScriptObject so = Invoke<ScriptObject>("GetLineColor");
                Debug.Assert(so == null, "No line color for pushpins");
                if (so == null)
                    return new VEColor(0, 0, 0, 1);	// return a default color
                VEColor color = new VEColor(so);
                return color;
            }
            set { Invoke("SetLineColor", (ScriptObject)value); }

		}

		/// <summary>The line width for a polyline or polygon.</summary>
		public int LineWidth
		{
			get
            {
                ScriptObject so = Invoke<ScriptObject>("GetLineWidth");
                Debug.Assert(so == null, "No line width for pushpins");
                if (so == null)
                    return 0;
                return so.ConvertTo<int>();
            }
            set { Invoke("SetLineWidth", value); }
		}

        /// <summary>Gets the z-index for a polyline or polygon.</summary>
        /// <returns type="int"></returns>
        public int ZIndexPolyShape
        {
            get
            {
                return Invoke<int>("GetZIndexPolyShape");
            }
        }
	}

	/// <summary>The VEPolygon class.</summary>
	public class VEPolygon : VEPolyShape
	{
		/// <summary>Construct a C# wrapper from a ScriptObject</summary>
		/// <param name="so">A ScriptObjet from JScript.</param>
		public VEPolygon(ScriptObject so) : base(so) { }

		/// <summary>Initializes a new instance of the VEPolygon class.</summary>
		/// <param name="points">Defines the array of points that define the Polygon, must contain at least 3 points. Required.</param>
		public VEPolygon(VELatLong[] points) : base(VEShapeType.Polygon, points) {}
		
		/// <summary>Gets or sets the fill color and transparency of a polygon.</summary>
		public VEColor FillColor
		{
			get
            {
                ScriptObject so = Invoke<ScriptObject>("GetFillColor");
                Debug.Assert(so == null, "No fill color for pushpins");
                if (so == null)
                    return new VEColor(0, 0, 0, 1);	// return a default color
                VEColor color = new VEColor(so);
                return color;
            }
            set { Invoke("SetFillColor", (ScriptObject)value); }
		}
		
	}

	/// <summary>The VEPolyline class.</summary>
	public class VEPolyline : VEPolyShape
	{
		/// <summary>Construct a C# wrapper from a ScriptObject</summary>
		/// <param name="so">A ScriptObjet from JScript.</param>
		public VEPolyline(ScriptObject so) : base(so) { }

		/// <summary>Initializes a new instance of the VEPolyline class.</summary>
		/// <param name="points">Defines the array of points that define the Polyline, must contain at least 2 points. Required.</param>
		public VEPolyline(VELatLong[] points) : base(VEShapeType.Polyline, points) {}
		
	}

    /// <summary>the VEPushpin class.</summary>
	public class VEPushpin : VEShape
	{
		/// <summary>Construct a C# wrapper from a ScriptObject</summary>
		/// <param name="so">A ScriptObjet from JScript.</param>
		public VEPushpin(ScriptObject so) : base(so) { }

        /// <summary>Initializes a new instance of the VEPushPin class.</summary>
        /// <remarks>
        /// This override creates a pushpin at 0,0.
        /// </remarks>
        public VEPushpin() : base(VEShapeType.Pushpin, VELatLong.Zero) { }

		/// <summary>Initializes a new instance of the VEPushPin class.</summary>
		/// <param name="point">Defines the location of the pushpin. Required.</param>
		public VEPushpin(VELatLong point) : base(VEShapeType.Pushpin, point) {}

		/// <summary>The point of the VEShape Class object.</summary>
		public VELatLong Point
		{
			get {return Points[0];}
            set { Points = new VELatLong[] { value };}
		}
	}

}
